package core.proto;

import java.awt.Adjustable;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.Highlighter.Highlight;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;

import org.apache.commons.io.FilenameUtils;

public class HighlightListener implements CaretListener {

	static final int FRAME_SIZE = 200;

	PaintDoc doc;
	static String lastCopiedCase; // the last case whose hilited  text was copied to notepad.
	JLayeredPane pane;
	HighlightToggle toggle; 
	
	public HighlightListener(PaintDoc doc, JLayeredPane pane, HighlightToggle toggle){
		this.doc = doc;
		this.pane = pane;
		this.toggle = toggle; 
		for(Annotation anno : AnnotationManager.getAnnotations(getDocName())){
			this.setUpAnnotation(anno, false);  
		}
	}


	private boolean overlaps(int start1, int end1, int start2, int end2){
		return (start1 <= start2 && start2 <= end1) ||
				(start1 <= end2 && end2 <= end1) ||
				(start2 <= start1 && start1 <= end2) ||
				(start2 <= end1 && end1 <= end2);
	}

	private String getDocName(){
		return FilenameUtils.getBaseName(doc.docKey);
	}

	/**
	 * 
	 * @param annotation
	 * @param useFrameView True if you want annotation to display using JInternalFrame, false otherwise. 
	 */
	private void setUpAnnotation(final Annotation annotation, boolean useFrameView){
		final JComponent frame = annotation.getFrame();
		final JTextArea textArea = annotation.getTextArea();

		// Create the text area to display when not editing. 
		final JTextArea readOnly = new JTextArea();
		readOnly.setEditable(false);
		readOnly.setWrapStyleWord(true);
		readOnly.setLineWrap(true);
		readOnly.setBackground(Color.yellow);
		readOnly.setBorder(BorderFactory.createLineBorder(Color.black));

		readOnly.addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent me){
				readOnly.setVisible(false);
				frame.setVisible(true);
				textArea.requestFocusInWindow();
			}
		});

		pane.add(readOnly, JLayeredPane.PALETTE_LAYER); 
		pane.add(frame, JLayeredPane.PALETTE_LAYER);
		frame.setSize(FRAME_SIZE, FRAME_SIZE);

		if (useFrameView){
			frame.setVisible(true); 
		}else {
			setUpReadonlyTextArea(readOnly, textArea.getText(), frame.getLocation(), textArea.getFontMetrics(textArea.getFont()));
			readOnly.setVisible(true);
		}

		textArea.addFocusListener(new FocusAdapter(){

			@Override
			public void focusLost(FocusEvent fe) {
				// If user left no comments, then don't save the annotation.  
				if (textArea.getText().trim().equals("")){
					AnnotationManager.removeAnnotation(getDocName(), annotation);
					pane.remove(frame);
					pane.remove(readOnly);
					pane.repaint();
					AnnotationManager.writeFile(getDocName()); // Save each time we update an annotation for this document.  
					return;
				}
				Point p = frame.getLocation();
				frame.setVisible(false);

				setUpReadonlyTextArea(readOnly, textArea.getText(), frame.getLocation(), textArea.getFontMetrics(textArea.getFont()));
				readOnly.setVisible(true);
				AnnotationManager.writeFile(getDocName()); // Save
			}
		});
	}

	private void setUpReadonlyTextArea(JTextArea readOnly, String text, Point location, FontMetrics metrics){
		readOnly.setText(text);
		try {
			// logic to fit the text to the read only text area.
			final int fudge = 4;
			int width = Math.min(FRAME_SIZE, metrics.stringWidth(text) + fudge);
			Dimension minimalSize = new Dimension(width,10);
			readOnly.setPreferredSize(minimalSize);
			readOnly.setSize(minimalSize); 
			Rectangle size = readOnly.modelToView(readOnly.getDocument().getLength());
			readOnly.setSize(new Dimension(width, (int) (size.getY() + size.getHeight())));
		} catch (BadLocationException e) {
		}
		readOnly.setLocation(location);
	}

	private boolean showModifyHighlightPrompt(){
		final String prompt = "The highlight is associated with an annotation. Really continue?"; 
		JOptionPane pane = new JOptionPane(prompt);
		Object[] options = new String[] { "Yes", "No" };
		pane.setOptions(options);
		JDialog dialog = pane.createDialog(new JFrame(), "Dilaog");
		dialog.setVisible(true);
		Object obj = pane.getValue(); 
		int result = -1;
		for (int k = 0; k < options.length; k++)
			if (options[k].equals(obj))
				result = k;
		return result == 0;
	}
	
	public void caretUpdate(final CaretEvent event) {
		/**
		 * Invariant: no two intervals i != j s.t. i, j overlaps. 
		 */
		int start = Math.min(event.getDot(), event.getMark());
		int end = Math.max(event.getDot(), event.getMark());

		if (start != end && toggle.isHighlighting()){
			Highlight[] hilites = doc.getHighlighter().getHighlights();
			try{
				List<Highlight> overlapping;
				for(Highlight hilite : hilites){
					if (hilite.getPainter() == doc.userHighlightPainter){
						// Removing some existing highlights. 
						if (hilite.getStartOffset() <= start && hilite.getEndOffset() >= end){
							// This is definitely more of a beta feature now since we haven't worked out how the "warning" 
							// shoudl look like. I suspect it's going to be more of an annoyance than feature for users.
							for (Annotation anno : AnnotationManager.getAnnotations(getDocName())){
								if (anno.getStartIndex() == hilite.getStartOffset() && anno.getEndIndex() == hilite.getEndOffset()){
									if (! anno.getFrame().isVisible() || ! anno.getTextArea().getText().equals("")){
										if (! showModifyHighlightPrompt()){
											// No removing. 
											return;
										} else {
											// Erase the annotated stuff. 
											anno.updateAnnotation(0, 0, "");
											AnnotationManager.writeFile(getDocName()); // Save each time annotation for this doc is updated. 
										}
									}
								}
							}


							doc.getHighlighter().removeHighlight(hilite);
							// Add back in the remaining highlight section. 
							if (hilite.getStartOffset() < start){
								doc.getHighlighter().addHighlight(hilite.getStartOffset(), start, doc.userHighlightPainter);
							}
							if (end < hilite.getEndOffset()){
								doc.getHighlighter().addHighlight(end, hilite.getEndOffset(), doc.userHighlightPainter);
							}
							HighlightManager.updateHighlights(doc.docKey, doc.getHighlighter().getHighlights(), doc.userHighlightPainter);
							// Done at this point since we removed some hilites. 
							return; 
						}
					}
				}
				// Join together all intervals that overlaps with the one we are about to add. 
				// This keeps the invariant that there are no overlapping intervals. 
				overlapping = new LinkedList<Highlight>();
				for(Highlight hilite : hilites){
					if (overlaps(hilite.getStartOffset(), hilite.getEndOffset(), start, end)){
						overlapping.add(hilite);
						doc.getHighlighter().removeHighlight(hilite);
					}
				}
				// Sort and merge overlapping intervals. 
				if (overlapping.size() > 0){
					Collections.sort(overlapping, new Comparator<Highlight>(){
						public int compare(Highlight left, Highlight right){
							if (left.getStartOffset() == right.getStartOffset())
								return 0;
							return left.getStartOffset() < right.getStartOffset() ? -1 : 1;
						}
					});
					// figure out the endpoints of the new, conjoined interval.
					start = Math.min(start, overlapping.get(0).getStartOffset());
					end = Math.max(end, overlapping.get(overlapping.size()-1).getEndOffset());
				}
				// Add the new highlight. 
				doc.getHighlighter().addHighlight(start, end, doc.userHighlightPainter);
				HighlightManager.updateHighlights(doc.docKey, doc.getHighlighter().getHighlights(), doc.userHighlightPainter);

				// Prompt user to enter annotation text. 
				String selectedText = doc.getDocument().getText(start, end - start);
				Point annotationPos = doc.getCaret().getMagicCaretPosition();
				// Always just add to the left margin of the document for now. 
				annotationPos.setLocation(0, doc.getCaret().getMagicCaretPosition().getY());
				Annotation annotation = new Annotation(getDocName(), start, end, doc.getCaret().getMagicCaretPosition(), selectedText, "");
				AnnotationManager.addAnnotation(getDocName(), annotation);
				setUpAnnotation(annotation, true);


				if (PaintDoc.isCopyingHighlightTextToNotepad()) {
					String caseAbsPath = "";
					// Remember that we copied text from this doc to the notepad, so that we will
					// only include the case name once when copying over to the notepad multiple times
					// in a row for the same document. 
					if (lastCopiedCase != doc.docKey){
						lastCopiedCase = doc.docKey;
						caseAbsPath = doc.docKey;
					}
					// Add text to the notepad. 
					AllComponents.addTextToNotepad(caseAbsPath, doc.getDocument().getText(start, end - start));
				}

			}catch(BadLocationException ble){
				System.err.println("Bad location");
			}
		}
	}
}
